package be.bonamis.umpire.business.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.joda.time.DateTime;
import org.joda.time.MutableDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import be.bonamis.umpire.business.UmpireService;
import be.bonamis.umpire.domain.Address;
import be.bonamis.umpire.domain.Club;
import be.bonamis.umpire.domain.GameType;
import be.bonamis.umpire.domain.LeagueDay;
import be.bonamis.umpire.domain.LeagueDay.Status;
import be.bonamis.umpire.domain.Person;
import be.bonamis.umpire.domain.Ranking;
import be.bonamis.umpire.domain.Umpire;
import be.bonamis.umpire.domain.UmpireLeagueDay;
import be.bonamis.umpire.domain.history.FairPlayReport;
import be.bonamis.umpire.domain.history.Game;
import be.bonamis.umpire.domain.history.UGCR;
import be.bonamis.umpire.domain.quiz.Answer;
import be.bonamis.umpire.domain.quiz.PersonQuiz;
import be.bonamis.umpire.domain.quiz.Question;
import be.bonamis.umpire.domain.quiz.Quiz;
import be.bonamis.umpire.history.ClubFPScore;
import be.bonamis.umpire.history.FPReportStat;
import be.bonamis.umpire.old.domain.CsvDispoArbitres;
import be.bonamis.umpire.old.domain.CsvDispoEvent;
import be.bonamis.umpire.old.domain.CsvDispoJournees;
import be.bonamis.umpire.old.domain.XlsCaUmpire;
import be.bonamis.umpire.old.domain.XlsRanking;
import be.bonamis.umpire.old.utils.ImportData;
import be.bonamis.umpire.quiz.IndividualResult;
import be.bonamis.umpire.quiz.PersonResult;
import be.bonamis.umpire.quiz.QuestionStat;
import be.bonamis.umpire.quiz.QuizReader;
import be.bonamis.umpire.quiz.QuizStat;
import be.bonamis.umpire.quiz.ResponseStyle;
import be.bonamis.umpire.repository.ClubRepository;
import be.bonamis.umpire.repository.GameRepository;
import be.bonamis.umpire.repository.GameTypeRepository;
import be.bonamis.umpire.repository.LeagueDayRepository;
import be.bonamis.umpire.repository.PersonQuizRepository;
import be.bonamis.umpire.repository.PersonRepository;
import be.bonamis.umpire.repository.QuizRepository;
import be.bonamis.umpire.repository.RankingRepository;
import be.bonamis.umpire.repository.ReportRepository;
import be.bonamis.umpire.repository.UGCRRepository;
import be.bonamis.umpire.repository.UmpireLeagueDayRepository;
import be.bonamis.umpire.repository.UmpireRepository;
import be.bonamis.umpire.utils.FileUtils;
import be.bonamis.umpire.utils.Mappings;

import com.google.common.io.ByteStreams;

//import static ch.l

@Service
public class UmpireServiceImpl implements UmpireService {

	private static final Logger LOGGER = LoggerFactory.getLogger(UmpireServiceImpl.class);

	@Autowired
	private RankingRepository rankingDao;

	@Autowired
	private ClubRepository clubDao;

	@Autowired
	private GameTypeRepository gameTypeDao;

	@Autowired
	private UmpireRepository umpireDao;

	@Autowired
	private LeagueDayRepository leagueDayDao;

	@Autowired
	private UmpireLeagueDayRepository umpireLeagueDayDao;

	@Autowired
	private QuizRepository quizDao;

	@Autowired
	private PersonQuizRepository personQuizDao;

	@Autowired
	private PersonRepository personDao;

	@Autowired
	private GameRepository gameDao;

	@Autowired
	private ReportRepository reportDao;

	@Autowired
	private UGCRRepository UGCRDao;

	private void importRankings(String fileName) {
		List<XlsRanking> list = FileUtils.read(fileName);
		for (XlsRanking xlsRanking : list) {
			Ranking ranking = new Ranking();
			ranking.setName(xlsRanking.getRanking());
			rankingDao.save(ranking);
		}
	}

	private void importClubs(String fileName) {
		List<Club> list = FileUtils.read(fileName);
		for (Club clubItem : list) {
			Club club = new Club();
			club.setCode(clubItem.getCode());
			club.setDimaSportCode(clubItem.getDimaSportCode());
			club.setName(clubItem.getName());
			club.setNumber(clubItem.getNumber());
			clubDao.save(club);
		}
	}

	private void importGameTypes(String fileName) {
		List<GameType> list = FileUtils.read(fileName);
		for (GameType gameType : list) {
			gameTypeDao.save(gameType);
		}
	}

	@Transactional
	@Override
	public void mergeOldDatabase(String arbitreCAFile, String dispoArbitreFile,
			String dispoJourneeFile, String dispoEventFile, String clubFile, String trainingFile,
			String gameTypeFile, String rankingFile, String beepTestFile) {

		if (umpireDao.findAll().size() == 0) {
			importClubs(clubFile);
			importGameTypes(gameTypeFile);
			importRankings(rankingFile);

			List<CsvDispoArbitres> oldDbUmpireList = ImportData
					.getOldDbUmpiresList(dispoArbitreFile);
			List<CsvDispoJournees> oldDbGameDaysList = ImportData
					.getOloldDbGameDaysList(dispoJourneeFile);
			List<CsvDispoEvent> oldDbEventsList = ImportData.getOloldDbEventssList(dispoEventFile);

			for (CsvDispoJournees csvDispoJournees : oldDbGameDaysList) {
				LeagueDay ld = Mappings.LeagueDays.fromOldDbtoNew.apply(csvDispoJournees);
				ld.setType(gameTypeDao.findByOldReference(csvDispoJournees.getTypeChamp()));
				leagueDayDao.save(ld);
			}

			for (CsvDispoArbitres dispoArbitres : oldDbUmpireList) {
				Umpire umpire = Mappings.Umpires.fromOldDbtoNew.apply(dispoArbitres);

				List<XlsCaUmpire> list = FileUtils.read(arbitreCAFile);
				XlsCaUmpire xlsCa = ImportData.getXlsCaUmpirebyId(list, dispoArbitres.getId());

				if (xlsCa != null) {
					umpire.setFirstName(xlsCa.getFirstName());
					umpire.setLastName(xlsCa.getName());

					Address adr = new Address();
					adr.setHouseNumber(xlsCa.getNbr());
					adr.setMunicipality(xlsCa.getCity());
					adr.setPostalCode(xlsCa.getZipCode());
					adr.setStreet(xlsCa.getStreet());
					umpire.setAddress(adr);

					umpire.setPhoneNumber(xlsCa.getGSM());

					SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy");
					try {
						umpire.setBirthDay(sdf.parse(xlsCa.getBirthdate()));
					} catch (ParseException e) {
						LOGGER.error("ParseException with the Birthdate: " + umpire.getFirstName()
								+ " " + umpire.getLastName());
					}

					if (StringUtils.isNotBlank(xlsCa.getClubName())) {
						if (!xlsCa.getClubName().equals("null")) {
							umpire.setClub(clubDao.findByName(xlsCa.getClubName()));
						}
					}

					umpire.setEmail(xlsCa.getEmail());
					umpire.setMemberId(Integer.valueOf(xlsCa.getMemberID()));

					umpire.setRanking(rankingDao.findByName(xlsCa.getDivision()));

					umpireDao.save(umpire);
				} else {
					LOGGER.debug(dispoArbitres.getNomArbitre() + " not found");
				}
			}

			for (CsvDispoEvent csvDispoEvent : oldDbEventsList) {
				UmpireLeagueDay day = new UmpireLeagueDay();

				Umpire ump = umpireDao.findByOldId(csvDispoEvent.getIdArbitr());

				if (ump != null) {
					LeagueDay leagueDay = leagueDayDao.findByOldId(csvDispoEvent.getIdJour());
					if (leagueDay != null) {
						day.setUmpire(ump);
						day.setUmpireId(ump.getId());

						day.setRemarque(csvDispoEvent.getRemarq());

						if (csvDispoEvent.getValRep() == 1) {
							day.setResponse(true);
						} else {
							day.setResponse(false);
						}

						day.setDay(leagueDay);
						day.setDayId(leagueDay.getId());

						day.setDayMonth(leagueDay.getDay().getMonthOfYear());
						day.setDayYear(leagueDay.getDay().getYear());

						umpireLeagueDayDao.save(day);

					}
				}
			}
		}
	}

	@Override
	public Umpire getUmpireByLoginAndPassword(String login, String password) {
		Umpire umpire = umpireDao.findByLogin(login);
		if (umpire != null) {
			if (umpire.getPassword().equals(DigestUtils.md5Hex(password))) {
				return umpire;
			}
		}
		return null;
	}

	@Override
	public void mergeAll() {
		if (umpireDao.count() == 0) {
			mergeOldDatabase("XlsCa.xls", "dispo_arbitres.csv", "dispo_journee.csv",
					"dispo_event.csv", "Clubs.xls", "Trainings.xls", "GameType.xls",
					"Rankings.xls", "BeepTests.xls");

			Umpire ump;
			String filename;

			filename = "broche.jpg";
			ump = umpireDao.findByLogin("broche");
			changePicture(ump, filename);

			filename = "seba.jpg";
			ump = umpireDao.findByLogin("sebdut");
			changePicture(ump, filename);

			filename = "ben.jpg";
			ump = umpireDao.findByLogin("copp1");
			changePicture(ump, filename);

			filename = "peps.jpg";
			ump = umpireDao.findByLogin("tigerpeps");
			changePicture(ump, filename);
		}
	}

	private void changePicture(Umpire ump, String filename) {
		try {
			InputStream in;
			byte[] picture;
			in = UmpireServiceImpl.class.getClassLoader().getResourceAsStream(filename);

			picture = ByteStreams.toByteArray(in);

			ump.setPicture(picture);
			umpireDao.save(ump);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public List<Umpire> getUmpiresList() {
		return umpireDao.findAll();
	}

	@Override
	public List<Club> getClubsList() {
		return clubDao.findAll();
	}

	@Override
	public Set<DateTime> getDispoMonthList() {
		List<LeagueDay> list = leagueDayDao.findAll();
		Set<DateTime> set = new TreeSet<DateTime>();
		for (LeagueDay leagueDay : list) {
			MutableDateTime mdt = new MutableDateTime(leagueDay.getDay());
			mdt.setDayOfMonth(1);
			mdt.setMillisOfDay(0); // if you want to make sure you're at
									// midnight
			set.add(mdt.toDateTime());
		}
		return set;
	}

	@Override
	public List<LeagueDay> getLeagueDayListByUmpireByMonth(long umpireId, int month, int year) {
		List<LeagueDay> list = new ArrayList<LeagueDay>();
		List<UmpireLeagueDay> dayList = umpireLeagueDayDao.findByUmpireIdAndDayMonthAndDayYear(
				umpireId, month, year);
		for (UmpireLeagueDay umpireLeagueDay : dayList) {
			list.add(umpireLeagueDay.getDay());
		}
		return list;
	}

	@Override
	public List<Umpire> getUmpiresListByLeagueDay(long leagueDayId) {
		List<Umpire> list = new ArrayList<Umpire>();
		List<UmpireLeagueDay> dayList = umpireLeagueDayDao.findByDayId(leagueDayId);
		for (UmpireLeagueDay umpireLeagueDay : dayList) {
			list.add(umpireLeagueDay.getUmpire());
		}
		Collections.sort(list);
		return list;
	}

	@Override
	public byte[] generateUmpireByLeaguedayXlsFile(long id) {
		List<Umpire> list = getUmpiresListByLeagueDay(id);
		Workbook wb = new HSSFWorkbook();
		Sheet sheet = wb.createSheet("new sheet");

		for (int i = 0; i < list.size(); i++) {
			Umpire umpireListElement = list.get(i);
			Row row = sheet.createRow((short) i);
			row.createCell(0).setCellValue(umpireListElement.getLastName());
			row.createCell(1).setCellValue(umpireListElement.getRanking().getName());
		}

		ByteArrayOutputStream os = new ByteArrayOutputStream();
		try {
			wb.write(os);
			if (os != null) {
				return os.toByteArray();
			}
		} catch (IOException e) {
			LOGGER.error(e.getMessage());
		}
		return null;
	}

	@Override
	public Umpire getUmpireById(long id) {
		return umpireDao.findOne((int) id);
	}

	@Transactional
	@Override
	public Quiz createQuizFromFile(String filename) {
		return quizDao.save(QuizReader.getQuizFromFile(filename));
	}

	@Transactional
	@Override
	public Quiz createQuiz(byte[] file) {
		return quizDao.save(QuizReader.getQuizFromStream(file));
	}

	@Override
	public Quiz getQuiz(long id) {
		return quizDao.findOne((int) id);
	}

	@Override
	public List<Quiz> getAllQuiz() {
		List<Quiz> list = quizDao.findAll();
		if (list.size() == 0) {
			try {
				InputStream in = UmpireServiceImpl.class.getClassLoader().getResourceAsStream(
						"Quizz_Number_1.xlsx");
				byte[] file = ByteStreams.toByteArray(in);
				createQuiz(file);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return quizDao.findAll();
	}

	@Override
	public Umpire putUmpire(Umpire umpire) {
		umpireDao.save(umpire);
		return umpire;
	}

	@Transactional
	@Override
	public IndividualResult umpirePutResponsesOnQuiz(long personid, List<Boolean> responses,
			long quizId) {
		PersonQuiz pq = new PersonQuiz();
		pq.setPersonId(personid);
		pq.setQuizId(quizId);
		pq.setResponses(responses);
		pq.setResponseDay(new DateTime());
		personQuizDao.save(pq);
		return getScore(quizId, responses);
	}

	@Override
	public IndividualResult getResponsesFromUmpireFromQuiz(long personId, long quizId) {
		List<PersonQuiz> list = personQuizDao.findByPersonIdAndQuizId(personId, quizId);
		if (list.size() > 0) {
			return getScore(quizId, list.get(0).getResponses());
		} else {
			return null;
		}
	}

	private IndividualResult getScore(long quizId, List<Boolean> responses) {
		IndividualResult result = new IndividualResult();

		Quiz quiz = quizDao.findOne((int) quizId);
		ArrayList<Boolean> goods = new ArrayList<Boolean>(Collections.nCopies(quiz.getQuestions()
				.size(), false));
		List<Boolean> list = new ArrayList<Boolean>();
		for (Question question : quiz.getQuestions()) {
			for (Answer answer : question.getAnswers()) {
				list.add(answer.isCorrect());
			}
		}

		ResponseStyle[] styles = new ResponseStyle[responses.size()];
		int correctQuestion = 0;
		List<Boolean> answers = new ArrayList<Boolean>();
		for (int row = 0; row < responses.size(); row++) {
			if (responses.get(row)) {
				if (list.get(row)) {
					answers.add(true);
					styles[row] = ResponseStyle.RIGHT;
				} else {
					styles[row] = ResponseStyle.WRONG;
					answers.add(false);
				}
			} else {
				if (list.get(row)) {
					styles[row] = ResponseStyle.RIGHT;
					answers.add(false);
				}
			}
			int answerNumber = row + 1;
			if (((answerNumber) % 4) == 0) {
				boolean test = true;
				for (Boolean boolean1 : answers) {
					test = test & boolean1;
				}
				if (test) {
					correctQuestion++;
					goods.set(((row + 1) / 4) - 1, true);
				}
				answers = new ArrayList<Boolean>();
			}
		}
		result.setResponses(responses);
		result.setScore(correctQuestion);
		result.setStyles(styles);
		result.setCorrectQuestions(goods);
		return result;
	}

	@Override
	public QuizStat createQuizStats(long quizId) {
		QuizStat quizStat = new QuizStat();
		List<QuestionStat> questionStats = new ArrayList<QuestionStat>();
		List<PersonResult> personResults = new ArrayList<PersonResult>();

		Quiz quiz = quizDao.findOne((int) quizId);
		int[] compteur = new int[quiz.getQuestions().size()];

		for (PersonQuiz personQuiz : personQuizDao.findByQuizId(quizId)) {
			IndividualResult result = getResponsesFromUmpireFromQuiz(personQuiz.getPersonId(),
					quiz.getId());

			PersonResult pr = new PersonResult();
			Person person = personDao.findOne((int) personQuiz.getPersonId());
			pr.setFullName(person.getLastName() + " " + person.getFirstName());
			pr.setScore(result.getScore());
			personResults.add(pr);

			List<Boolean> cq = result.getCorrectQuestions();
			for (int i = 0; i < cq.size(); i++) {
				if (cq.get(i)) {
					compteur[i]++;
				}
			}
		}

		for (int i = 0; i < quiz.getQuestions().size(); i++) {
			QuestionStat qs = new QuestionStat();
			qs.setQuestion(quiz.getQuestions().get(i).getQuestionFr());
			qs.setResult(compteur[i]);
			questionStats.add(qs);
		}
		// Comparator<QuestionStat> comp = Collections.reverseOrder();
		Collections.sort(questionStats);

		Comparator<PersonResult> comp2 = Collections.reverseOrder();
		Collections.sort(personResults, comp2);

		quizStat.setPersonResults(personResults);
		quizStat.setQuestionStats(questionStats);
		return quizStat;
	}

	@Override
	public List<Game> findGamesByUmpire(Integer id) {
		return gameDao.findByUmpire01IdOrUmpire02Id(id, id);
	}

	@Override
	public FPReportStat getFPResultByDivision() {
		Map<String, Integer> treeMap = new TreeMap<String, Integer>();
		List<UGCR> list = UGCRDao.findAll();

		for (UGCR ugcr : list) {
			Club club = clubDao.findOne((int) ugcr.getClubId());
			int score = 0;
			if (treeMap.containsKey(club.getName())) {
				score = treeMap.get(club.getName());
			}
			score += getScore(reportDao.findOne((int) ugcr.getReportId()));
			treeMap.put(club.getName(), score);
		}

		Collection<Entry<String, Integer>> entrySet = treeMap.entrySet();
		Iterator<Entry<String, Integer>> it = entrySet.iterator();
		List<ClubFPScore> scores = new ArrayList<ClubFPScore>();
		while (it.hasNext()) {
			Entry<String, Integer> entry = it.next();
			ClubFPScore score = new ClubFPScore();
			score.setClubName(entry.getKey());
			score.setScore(entry.getValue());
			scores.add(score);
		}

		FPReportStat stat = new FPReportStat();
		Collections.sort(scores);
		stat.setScores(scores);
		return stat;
	}

	// private static LinkedHashMap<String, Integer>
	// sortByComparator(Map<String, Integer> unsortMap) {
	//
	// List<Entry<String, Integer>> list = new LinkedList<Entry<String,
	// Integer>>(unsortMap.entrySet());
	//
	// // sort list based on comparator
	// Collections.sort(list, new Comparator() {
	// public int compare(Object o1, Object o2) {
	// return ((Comparable) ((Map.Entry) (o2)).getValue())
	// .compareTo(((Map.Entry) (o1)).getValue());
	// }
	// });
	//
	// // put sorted list into map again
	// //LinkedHashMap make sure order in which keys were inserted
	// LinkedHashMap<String, Integer> sortedMap = new LinkedHashMap<String,
	// Integer>();
	// for (Iterator<Entry<String, Integer>> it = list.iterator();
	// it.hasNext();) {
	// Map.Entry<String, Integer> entry = it.next();
	// sortedMap.put(entry.getKey(), entry.getValue());
	// }
	// return sortedMap;
	// }

	private Integer getScore(FairPlayReport report) {

		return checkNull(report.getAccueil()) + checkNull(report.getAccueilDélégués())
				+ checkNull(report.getComportementCapitaine())
				+ checkNull(report.getComportementJoueurs())
				+ checkNull(report.getComportementPublic())
				+ checkNull(report.getComportementStaff()) + checkNull(report.getLineUp());
	}

	private Integer checkNull(Integer inte) {
		if (inte == null) {
			return 0;
		} else {
			return inte;
		}
	}

	@Override
	public FPReportStat putUmpireFPReport(Integer umpireId, Integer gameId,
			FairPlayReport report01, FairPlayReport report02) {
		UGCR ugcr03 = new UGCR();
		Game game = gameDao.findOne(gameId);
		ugcr03.setClubId(game.getHomeClub().getId());
		ugcr03.setGameId(gameId);
		ugcr03.setReportId(reportDao.save(report01).getId());
		ugcr03.setUmpireId(umpireId);
		UGCRDao.save(ugcr03);

		UGCR ugcr04 = new UGCR();
		ugcr04.setClubId(game.getAwayClub().getId());
		ugcr04.setGameId(gameId);
		ugcr04.setReportId(reportDao.save(report02).getId());
		ugcr04.setUmpireId(umpireId);
		UGCRDao.save(ugcr04);
		return getFPResultByDivision();
	}

	@Override
	public Status getUmpireLeagueDayResponse(int umpireId, int leagueDayId) {
		List<UmpireLeagueDay> list = umpireLeagueDayDao.findByUmpireIdAndDayId(umpireId,
				leagueDayId);
		if (list.size() == 0) {
			return Status.NO_ANSWER;
		} else {
			if (list.get(0).isResponse()) {
				return Status.YES;
			} else {
				return Status.NO;
			}
		}
	}

	@Override
	public Status putUmpireLeagueDayResponse(int umpireId, int leagueDayId, Status status) {
		Status statusDb = getUmpireLeagueDayResponse(umpireId, leagueDayId);
		if (statusDb == Status.NO_ANSWER) {
			UmpireLeagueDay day = new UmpireLeagueDay();
			day.setUmpire(umpireDao.findOne(umpireId));
			day.setUmpireId(umpireDao.findOne(umpireId).getId());
			LeagueDay leagueDay = leagueDayDao.findOne(leagueDayId);
			day.setDay(leagueDay);
			day.setDayId(leagueDay.getId());
			day.setDayMonth(leagueDay.getDay().getMonthOfYear());
			day.setDayYear(leagueDay.getDay().getYear());
			boolean response = false;
			if (status == Status.YES) {
				response = true;
			}
			day.setResponse(response);
			umpireLeagueDayDao.save(day);
			statusDb = getUmpireLeagueDayResponse(umpireId, leagueDayId);
		} else {
			List<UmpireLeagueDay> list = umpireLeagueDayDao.findByUmpireIdAndDayId(umpireId,
					leagueDayId);
			UmpireLeagueDay day = list.get(0);
			boolean response = false;
			if (status == Status.YES) {
				response = true;
			}
			day.setResponse(response);
			umpireLeagueDayDao.save(day);
			statusDb = getUmpireLeagueDayResponse(umpireId, leagueDayId);
		}
		return statusDb;
	}
}
